package page;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.util.Scanner;

import queue.BlockingObject;
import queue.PageBuffer;

/**
 * Retrieves source of web page.
 * 
 * @author Ken "David" McClain
 * @author Peter Mosher
 * @version 1
 */
public class PageRetriever extends Thread {
	
	/** URL to retrieve. */
	private String url;
	
	/** Whether or not we are busy (retrieving). */
	private boolean busy = false;
	
	/** Queue we will add the page to. */
	private PageBuffer pageBuffer;
	
	/** 
	 * Object PageToRetrieve waits on when no threads are available.
	 * We notify this object when we complete, to "wake" the thread up.
	 */
	private BlockingObject blockToNotify, blockedUntilFinished;
	
	/**
	 * Default constructor, does nothing. 
	 * Called by Single-threaded implementation.
	 */
	public PageRetriever() { }
	
	
	/**
	 * Constructor for threaded retrieving. Stores PageBuffer 
	 * @param pageBuffer the PageBuffer queue to store retrieved pages in.
	 */
	public PageRetriever(final PageBuffer pageBuffer) {
		this.pageBuffer = pageBuffer;
	}
	
	/**
	 * @param url The next URL to retrieve (using 
	 */
	public void setURL(final String url) {
		this.url = url;
	}
	
	/**
	 * Set the object checked by PageToRetrieve.
	 * Notifying this object wakes up the PageToRetrieve loop, if it was
	 * waiting for threads to become available.
	 * @param blockToNotify The object to notify when this thread finishes executing.
	 */
	public void setBlockingObject(final BlockingObject blockToNotify) {
		this.blockToNotify = blockToNotify;
	}
	
	/**
	 * Set the object checked by ModelMulti.
	 * Notifying this object wakes up the ModelMulti thread, which checks
	 * if the program has run into a dead end.
	 * @param blockedUntilFinished The object to notify when thread finishes executing.
	 */
	public void setBlockedUntilFinished(final BlockingObject blockedUntilFinished) {
		this.blockedUntilFinished = blockedUntilFinished;
	}
	
	/**
	 * @param busy True if the thread is busy, false otherwise.
	 */
	synchronized public void setBusy(boolean busy) {
		this.busy = busy;
	}
	
	
	/**
	 * Retrieves page at 'url' (instance field). Threaded.
	 */
	public void run() {
		setBusy(true);
		
		// Get the web page data.
		String pageSource = getHTML(url);
		
		// If the page source is not empty,
		if (!"".equals(pageSource)) {
			// Store the page data into the page buffer queue.
			pageBuffer.add(new Page(url, pageSource));
		}
		
		setBusy(false);
		
		// Notify PageToRetrieve in case they were waiting for
		// a thread to become available.
		synchronized (blockToNotify) {
			blockToNotify.notify();
		}
		
		// Notify ModelMulti that this thread has finished.
		// This wakes up the main loop and checks if we are done.
		synchronized(blockedUntilFinished) {
			blockedUntilFinished.notify();
		}
		
	}
	
	/**
	 * @return True if thread is busy retrieving a page, false otherwise.
	 */
	synchronized public boolean isBusy() { return busy; }
	
	
	/**
	 * Retrieves HTML text from a given URL. Catches all exceptions.
	 * 
	 * Called directly by single-threaded implementation,
	 * called by start() for multi-threaded implementation.
	 * 
	 * @return HTML code (body) for a URL, or empty string ("") 
	 * 			if page could not be retrieved 
	 */
	public String getHTML(final String theURL) {
		// Result is a StringBuilder (uses less memory than a String)
		StringBuilder result = new StringBuilder();
        
        try {
        	// Open new connection
        	final URLConnection uc = new URL(theURL).openConnection();
        	
        	// The "timeouts" below are how long the socket waits before stopping
        	// These timeouts prevent us from being stuck on a page that never loads.
        	// The waiting times are in milliseconds
        	
			uc.setConnectTimeout(5 * 1000); // wait a maximum of 5 seconds before giving up.
			uc.setReadTimeout(5 * 1000);    // wait a maximum of 5 seconds for data to be sent.
			
			// The User-agent property of our request tells the web server what kind of computer we are running
			// Some websites block certain user-agents, so I will use the Mozilla Firefox user agent.
			// This makes the web server *think* we are using Firefox.
			uc.setRequestProperty("User-Agent", 
				"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) " +
				"Gecko/20101203 Firefox/3.6.13");
			
			// I like to use Scanner to read input streams (it's simple).
			final Scanner in = new Scanner(uc.getInputStream());
			
	        while (in.hasNextLine())          // Read every line from the web page input stream.
	        	result.append(in.nextLine()); // Add every line to the string buffer.
	        
	        in.close(); // Close the input stream.
	        
	        
	        // Lots of exceptions can occur when requesting a file over the web.
        } catch (final FileNotFoundException fnf) { 
        	//System.err.println("*** FileNotFoundException: " + theURL);
        	return "";
        } catch (final UnknownHostException uhe) {
        	//System.err.println("*** UnknownHostException: " + theURL);
        	return "";
        } catch (final MalformedURLException mue) {
        	//System.err.println("*** MalformedURLException: " + theURL);
        	return "";
        } catch (final SocketTimeoutException ste) { 
        	//System.err.println("*** SocketTimeoutException: " + theURL);
        	return "";
        } catch (final IllegalArgumentException iae) {
        	//System.err.println("*** IllegalArgumentException: " + theURL);
        	return "";
        } catch (final IOException ioe) { 
        	//System.err.println("*** IOException: " + theURL);
        	return "";
        }
        
        return result.toString();
	}
	
	public String toString() {
		return "<PageRetriever, URL='" + url + "'>";
	}
}
